<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Common;

final class CommString
{
    private static bool $crc64Init          = false;    // CRC64初始化状态
    private static array $crc64TableList    = [];       // CRC64

    /**
     * @param string $string
     * @return int
     * strLen
     */
    public static function strLen(string $string):int
    {
        return strlen($string);
    }

    /**
     * @param string $string
     * @return int
     * strLenWord
     */
    public static function strLenWord(string $string):int
    {
        $wordLen    = grapheme_strlen($string);
        return is_int($wordLen) ? $wordLen : 0;
    }

    /**
     * @param string $string
     * @param string $needle
     * @param int $offset
     * @return int
     * strPos
     */
    public static function strPos(string $string, string $needle, int $offset = 0):int
    {
        $rPos       = strpos($string, $needle, $offset);
        return is_int($rPos) ? $rPos : -1;
    }

    /**
     * @param string $string
     * @param string $needle
     * @param int $offset
     * @return int
     * strPosWord
     */
    public static function strPosWord(string $string, string $needle, int $offset = 0):int
    {
        $rPos       = grapheme_strpos($string, $needle, $offset);
        return is_int($rPos) ? $rPos : -1;
    }

    /**
     * @param string $string
     * @param string $needle
     * @param int $offset
     * @return int
     * strRPos
     */
    public static function strRPos(string $string, string $needle, int $offset = 0):int
    {
        $rPos       = strrpos($string, $needle, $offset);
        return is_int($rPos) ? $rPos : -1;
    }

    /**
     * @param string $string
     * @param string $needle
     * @param int $offset
     * @return int
     * strRPosWord
     */
    public static function strRPosWord(string $string, string $needle, int $offset = 0):int
    {
        $rPos       = grapheme_strrpos($string, $needle, $offset);
        return is_int($rPos) ? $rPos : -1;
    }

    /**
     * @param string $string
     * @param int $offset
     * @param int|null $length
     * @return string
     * strSub
     */
    public static function strSub(string $string, int $offset, ?int $length = null):string
    {
        return substr($string, $offset, $length);
    }

    /**
     * @param string $string
     * @param int $offset
     * @param int|null $length
     * @param bool $result
     * @return string
     * strSubWord
     */
    public static function strSubWord(string $string, int $offset, ?int $length = null, bool &$result = true):string
    {
        $resStr     = grapheme_substr($string, $offset, $length);
        if(is_string($resStr)){
            $result = true;
            return $resStr;
        }else{
            $result = false;
            return '';
        }
    }

    /**
     * @param string $string
     * @param int $offset
     * @param int|null $length
     * @param bool $result
     * @return string
     * strSubWordCharLength
     */
    public static function strSubWordCharLength(string $string, int $offset, ?int $length = null, bool &$result = true):string
    {
        $wordLen    = self::strLenWord(self::strSub($string, $offset, $length));
        if($wordLen < 1){
            $result = false;
            return '';
        }
        $resStr     = self::strSubWord($string, $offset, $wordLen, $result);
        if($result && self::strLen($resStr) <= $length) return $resStr;
        $resStr     = self::strSubWord($string, $offset, $wordLen - 1, $result);
        return $result ? $resStr : '';
    }

    /**
     * @param string $string
     * @return bool
     * isEmpty
     */
    public static function isEmpty(string $string):bool
    {
        return self::strLen($string) < 1;
    }

    /**
     * @param string $string
     * @param string $startStr
     * @return bool
     * startsWith
     */
    public static function startsWith(string $string, string $startStr):bool
    {
        $stringLen      = self::strLen($string);
        if($stringLen < 1) return false;
        $startStrLen    = self::strLen($startStr);
        if($startStrLen < 1 || $startStrLen > $stringLen) return false;
        return self::strSub($string, 0, $startStrLen) === $startStr;
    }

    /**
     * @param string $string
     * @return string
     * trimAll
     */
    public static function trimAll(string $string):string
    {
        if(self::isEmpty($string)) return '';
        return preg_replace_callback("/\s+/", static function(){ return ''; }, $string);
    }

    /**
     * @param string $string
     * @param string $trimStr
     * @return string
     * trimLeft
     */
    public static function trimLeft(string $string, string $trimStr = ''):string
    {
        $trimStrLength  = self::strLenWord($trimStr);
        if($trimStrLength < 1) return ltrim($string);
        if($trimStrLength === self::strLen($trimStr)) return ltrim($string, $trimStr);
        $stringLength   = self::strLenWord($string);
        $trimStrList    = [];
        while(--$trimStrLength >= 0) $trimStrList[] = self::strSubWord($trimStr, $trimStrLength, 1);
        $stringIndex    = 0;
        while($stringIndex < $stringLength){
            $tmpStr     = self::strSubWord($string, $stringIndex++, 1);
            if(!in_array($tmpStr, $trimStrList, true)) return self::strSubWord($string, --$stringIndex);
        }
        return '';
    }

    /**
     * @param string $string
     * @param string $trimStr
     * @return string
     * trimRight
     */
    public static function trimRight(string $string, string $trimStr = ''):string
    {
        $trimStrLength  = self::strLenWord($trimStr);
        if($trimStrLength < 1) return rtrim($string);
        if($trimStrLength === self::strLen($trimStr)) return rtrim($string, $trimStr);
        $stringLength   = self::strLenWord($string);
        $trimStrList    = [];
        while(--$trimStrLength >= 0) $trimStrList[] = self::strSubWord($trimStr, $trimStrLength, 1);
        while(--$stringLength >= 0){
            $tmpStr     = self::strSubWord($string, $stringLength, 1);
            if(!in_array($tmpStr, $trimStrList, true)) return self::strSubWord($string, 0, $stringLength + 1);
        }
        return '';
    }

    /**
     * @param string $string
     * @param string $trimStr
     * @return string
     * trim
     */
    public static function trim(string $string, string $trimStr = ''):string
    {
        $trimStrLength          = self::strLenWord($trimStr);
        if($trimStrLength < 1) return trim($string);
        if($trimStrLength === self::strLen($trimStr)) return trim($string, $trimStr);
        $stringLength           = self::strLenWord($string);
        $trimStrList            = [];
        while(--$trimStrLength >= 0) $trimStrList[] = self::strSubWord($trimStr, $trimStrLength, 1);
        // left
        $stringIndex            = 0;
        while($stringIndex < $stringLength){
            $tmpStr             = self::strSubWord($string, $stringIndex++, 1);
            if(!in_array($tmpStr, $trimStrList, true)){
                $string         = self::strSubWord($string, --$stringIndex);
                $stringLength   = self::strLenWord($string);
                break;
            }
        }
        // right
        while(--$stringLength >= 0){
            $tmpStr     = self::strSubWord($string, $stringLength, 1);
            if(!in_array($tmpStr, $trimStrList, true)) return self::strSubWord($string, 0, $stringLength + 1);
        }
        return '';
    }

    /**
     * @param string $string
     * @param string $outEncoding
     * @param string $inEncoding
     * @return string
     * strConvert
     */
    public static function strConvert(string $string, string $outEncoding, string $inEncoding = ''):string
    {
        self::isEmpty($inEncoding) && $inEncoding = mb_internal_encoding();
        return mb_convert_encoding($string, $outEncoding, $inEncoding);
    }

    /**
     * @param string $string
     * @param int $wordNum
     * @return array|string[]
     * strSplit
     */
    public static function strSplit(string $string, int $wordNum):array
    {
        if($wordNum < 1) return [];
        $strLenWord = self::strLenWord($string);
        if($strLenWord < 1) return [];
        if($wordNum >= $strLenWord) return [$string];
        $splitIndex = 0;
        $splitNum   = intval(ceil($strLenWord/$wordNum));
        $splitAry   = [];
        while(++$splitIndex <= $splitNum){
            $result     = false;
            $wordStr    = self::strSubWord($string, ($splitIndex - 1) * $wordNum, $wordNum, $result);
            if($result) $splitAry[] = $wordStr;
        }
        return $splitAry;
    }

    /**
     * @param string $string
     * @return bool
     * strIsUtf8
     */
    public static function strIsUtf8(string $string):bool
    {
        $length         = strlen($string);
        for($i = 0; $i < $length; $i++){
            $charInt    = ord($string[$i]);
            if($charInt > 128){
                if(($charInt >= 254)) return false;
                elseif($charInt >= 252) $bits = 6;
                elseif($charInt >= 248) $bits = 5;
                elseif($charInt >= 240) $bits = 4;
                elseif($charInt >= 224) $bits = 3;
                elseif($charInt >= 192) $bits = 2;
                else return false;
                if(($i + $bits) > $length) return false;
                while($bits > 1){
                    ++$i;
                    $ordInt = ord($string[$i]);
                    if($ordInt < 128 || $ordInt > 191) return false;
                    --$bits;
                }
            }
        }
        return true;
    }

    /**
     * @param int $strLen
     * @param string $limitStr
     * @param string $limitFailChar
     * @return string
     * genRandStr
     */
    public static function genRandStr(int $strLen, string $limitStr = '', string $limitFailChar = ''):string
    {
        $resStr         = '';
        if($strLen < 1) return $resStr;
        $limitStrLen    = self::strLenWord($limitStr);
        if($limitStrLen > 0){
            while(--$strLen >= 0){
                $result     = false;
                $wordStr    = self::strSubWord($limitStr, mt_rand(0, $limitStrLen - 1), 1, $result);
                $resStr     .= $result ? $wordStr : $limitFailChar;
            }
        }else{
            while(--$strLen >= 0){
                switch(mt_rand(0, 2)){
                    case 0:{
                        $resStr .= chr(mt_rand(65, 90));
                        break;
                    }
                    case 1:{
                        $resStr .= chr(mt_rand(97, 122));
                        break;
                    }
                    default:{
                        $resStr .= mt_rand(0, 9);
                        break;
                    }
                }
            }
        }
        return $resStr;
    }

    /**
     * @param int $strLen
     * @param bool $nonZeroStart
     * @return string
     * genRandStrNum
     */
    public static function genRandStrNum(int $strLen, bool $nonZeroStart = false):string
    {
        $resStr = '';
        if($strLen < 1) return $resStr;
        if($nonZeroStart) $resStr .= mt_rand(1, 9);
        while(strlen($resStr) < $strLen) $resStr .= mt_rand(0, 9);
        return $resStr;
    }

    /**
     * @param int $strLen
     * @return string
     * genRandStrLetter
     */
    public static function genRandStrLetter(int $strLen):string
    {
        $resStr = '';
        if($strLen > 0) while(strlen($resStr) < $strLen) $resStr .= chr(mt_rand(0, 1) ? mt_rand(65, 90) : mt_rand(97, 122));
        return $resStr;
    }

    /**
     * @param int $strLen
     * @return string
     * genRandStrLetterUpper
     */
    public static function genRandStrLetterUpper(int $strLen):string
    {
        $resStr = '';
        if($strLen > 0) while(strlen($resStr) < $strLen) $resStr .= chr(mt_rand(65, 90));
        return $resStr;
    }

    /**
     * @param int $strLen
     * @return string
     * genRandStrLetterLower
     */
    public static function genRandStrLetterLower(int $strLen):string
    {
        $resStr = '';
        if($strLen > 0) while(strlen($resStr) < $strLen) $resStr .= chr(mt_rand(97, 122));
        return $resStr;
    }

    /**
     * @return string
     * genRandStrColor
     */
    public static function genRandColorStr(bool $alpha = false, int $alphaPercent = -1):string
    {
        $colorList          = [mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255)];
        if($alpha){
            $alphaPercent   = min($alphaPercent, 100);
            $colorList[]    = $alphaPercent >= 0 ? intval(round(255*$alphaPercent/100)) : mt_rand(0, 255);
        }
        $colorStr           = '';
        foreach ($colorList as $colorInt) $colorStr .= str_pad(dechex($colorInt), 2, '0', STR_PAD_LEFT);
        return strtoupper($colorStr);
    }

    /**
     * @param string $colorStr
     * @param int $colorR
     * @param int $colorG
     * @param int $colorB
     * @param int $colorA
     * @return bool
     * parseColorStr
     */
    public static function parseColorStr(string $colorStr, int &$colorR, int &$colorG, int &$colorB, int &$colorA):bool
    {
        $colorR = $colorG = $colorB = $colorA = -1;
        if(preg_match('/^([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i', $colorStr, $match)){
            $colorR     = intval(CommString::hexToDec($match[1]));
            $colorG     = intval(CommString::hexToDec($match[2]));
            $colorB     = intval(CommString::hexToDec($match[3]));
            $colorA     = intval(CommString::hexToDec($match[4]));
            return true;
        }
        if(preg_match('/^([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i', $colorStr, $match)){
            $colorR     = intval(CommString::hexToDec($match[1]));
            $colorG     = intval(CommString::hexToDec($match[2]));
            $colorB     = intval(CommString::hexToDec($match[3]));
            return true;
        }
        if(preg_match('/^([a-f\d])([a-f\d])([a-f\d])([a-f\d])$/i', $colorStr, $match)){
            $colorR     = intval(CommString::hexToDec($match[1].$match[1]));
            $colorG     = intval(CommString::hexToDec($match[2].$match[2]));
            $colorB     = intval(CommString::hexToDec($match[3].$match[3]));
            $colorA     = intval(CommString::hexToDec($match[4].$match[4]));
            return true;
        }
        if(preg_match('/^([a-f\d])([a-f\d])([a-f\d])$/i', $colorStr, $match)){
            $colorR     = intval(CommString::hexToDec($match[1].$match[1]));
            $colorG     = intval(CommString::hexToDec($match[2].$match[2]));
            $colorB     = intval(CommString::hexToDec($match[3].$match[3]));
            return true;
        }
        return false;
    }

    /**
     * @return string
     * genRandStrUuId
     */
    public static function genRandStrUuId():string
    {
        $uniqIdStr  = strtoupper(sha1(uniqid('UUID', true).self::genRandStr(mt_rand(8, 12))));
        return substr($uniqIdStr, 0, 8).'-'.substr($uniqIdStr, 8, 4).'-'.substr($uniqIdStr, 12, 4).'-'.substr($uniqIdStr, 16, 4).'-'.substr($uniqIdStr, 20, 12);
    }

    /**
     * @return string
     * genRandStrGuId
     */
    public static function genRandStrGuId():string
    {
        $uniqIdStr  = strtoupper(sha1(uniqid('GUID', true).self::genRandStr(mt_rand(8, 12))));
        return substr($uniqIdStr, 0, 8).'-'.substr($uniqIdStr, 8, 4).'-'.substr($uniqIdStr, 12, 4).'-'.substr($uniqIdStr, 16, 16);
    }

    /**
     * @param string $string
     * @param bool $isLower
     * @return string
     * strToHex
     */
    public static function strToHex(string $string, bool $isLower):string
    {
        if(self::isEmpty($string)) return '';
        $unPackAry  = @unpack('H*', $string);
        if(isset($unPackAry[1]) && is_string($unPackAry[1]) && !self::isEmpty($unPackAry[1])){
            return $isLower ? strtolower($unPackAry[1]) : strtoupper($unPackAry[1]);
        }
        return '';
    }

    /**
     * @param string $hexStr
     * @return string
     * hexToStr
     */
    public static function hexToStr(string $hexStr):string
    {
        if(self::isEmpty($hexStr) || !preg_match('/^[a-fA-F\d]+$/', $hexStr)) return '';
        return @pack('H*', $hexStr);
    }

    /**
     * @param string $string
     * @return string
     * strToBin
     */
    public static function strToBin(string $string):string
    {
        if(self::isEmpty($string)) return '';
        $binStr     = '';
        $unPackAry  = @unpack('H*', $string);
        if(isset($unPackAry[1]) && is_string($unPackAry[1]) && !self::isEmpty($unPackAry[1])){
            foreach(str_split($unPackAry[1]) as $hexChar) $binStr .= str_pad(base_convert($hexChar, 16, 2), 4, '0', STR_PAD_LEFT);
        }
        return $binStr;
    }

    /**
     * @param string $binStr
     * @return string
     * binToStr
     */
    public static function binToStr(string $binStr):string
    {
        if(self::isEmpty($binStr) || !preg_match('/^[01]+$/', $binStr)) return '';
        $hexStr     = '';
        foreach(str_split($binStr, 4) as $binStrHex) $hexStr .= base_convert($binStrHex, 2, 16);
        return @pack('H*', $hexStr);
    }

    /**
     * @param string $hexStr
     * @return string
     * hexToBin
     */
    public static function hexToBin(string $hexStr):string
    {
        if(self::isEmpty($hexStr) || !preg_match('/^[a-fA-F\d]+$/', $hexStr)) return '';
        $binStr     = '';
        foreach(str_split($hexStr) as $val) $binStr .= str_pad(base_convert($val, 16, 2), 4, '0', STR_PAD_LEFT);
        return $binStr;
    }

    /**
     * @param string $binStr
     * @param bool $isLower
     * @return string
     * binToHex
     */
    public static function binToHex(string $binStr, bool $isLower):string
    {
        if(self::isEmpty($binStr) || !preg_match('/^[01]+$/', $binStr)) return '';
        $hexStr = '';
        foreach(str_split($binStr, 4) as $binStrHex) $hexStr .= base_convert($binStrHex, 2, 16);
        return $isLower ? strtolower($hexStr) : strtoupper($hexStr);
    }

    /**
     * @param string $char64Str
     * @return string
     * char64ToBin
     */
    public static function char64ToBin(string $char64Str):string
    {
        if(self::isEmpty($char64Str) || !preg_match('/^[a-zA-Z+\d\/]+[!_\-=~]?$/', $char64Str)) return '';
        $binStr             = '';
        $endLengthList      = ['!'=>1,'_'=>2,'-'=>3,'='=>4,'~'=>5];
        $charList           = [
            '0'=>0,'1'=>1,'2'=>2,'3'=>3,'4'=>4,'5'=>5,'6'=>6,'7'=>7,'8'=>8,'9'=>9,
            'A'=>10,'B'=>11,'C'=>12,'D'=>13,'E'=>14,'F'=>15,'G'=>16,'H'=>17,'I'=>18,'J'=>19,'K'=>20,'L'=>21,'M'=>22,'N'=>23,'O'=>24,'P'=>25,'Q'=>26,'R'=>27,'S'=>28,'T'=>29,'U'=>30,'V'=>31,'W'=>32,'X'=>33,'Y'=>34,'Z'=>35,
            'a'=>36,'b'=>37,'c'=>38,'d'=>39,'e'=>40,'f'=>41,'g'=>42,'h'=>43,'i'=>44,'j'=>45,'k'=>46,'l'=>47,'m'=>48,'n'=>49,'o'=>50,'p'=>51,'q'=>52,'r'=>53,'s'=>54,'t'=>55,'u'=>56,'v'=>57,'w'=>58,'x'=>59,'y'=>60,'z'=>61,
            '+'=>62,'/'=>63,
        ];
        $char64StrList      = str_split($char64Str, 1);
        $char64StrLength    = count($char64StrList);
        $endLengthNum       = $endLengthList[$char64StrList[$char64StrLength-1]]??0;
        if($endLengthNum > 0){
            unset($char64StrList[$char64StrLength-1]);
            $char64StrLength    = count($char64StrList);
        }
        foreach($char64StrList as $key => $val){
            if(!isset($charList[$val]) || !is_int($charList[$val]) || $charList[$val] < 0 || $charList[$val] > 63) return '';
            if($key+1 === $char64StrLength && $endLengthNum > 0){
                $binStr     .= substr(str_pad(base_convert(strval($charList[$val]), 10, 2), 6, '0', STR_PAD_LEFT), $endLengthNum*-1);
            }else{
                $binStr     .= str_pad(base_convert(strval($charList[$val]), 10, 2), 6, '0', STR_PAD_LEFT);
            }
        }
        return $binStr;
    }

    /**
     * @param string $binStr
     * @return string
     * binToChar64
     */
    public static function binToChar64(string $binStr):string
    {
        if(self::isEmpty($binStr) || !preg_match('/^[01]+$/', $binStr)) return '';
        $char64Str          = '';
        $endLengthList      = [1=>'!',2=>'_',3=>'-',4=>'=',5=>'~'];
        $charList           = [
            '0','1','2','3','4','5','6','7','8','9',
            'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
            'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
            '+','/',
        ];
        foreach(str_split($binStr, 6) as $binStrItem){
            $charIndex          = intval(base_convert($binStrItem, 2, 10));
            if(!isset($charList[$charIndex])) return '';
            $char64Str          .= $charList[$charIndex];
            $binStrItemLength   = strlen($binStrItem);
            if($binStrItemLength < 6) $char64Str .= $endLengthList[$binStrItemLength];
        }
        return $char64Str;
    }

    /**
     * @param string $decStr
     * @return string
     * decToHex
     */
    public static function decToHex(string $decStr):string
    {
        if($decStr === '0') return '0';
        if(self::isEmpty($decStr) || !preg_match('/^\d+$/', $decStr)) return '';
        $hexValueList   = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
        $hexVal         = '';
        while($decStr !== '0'){
            $hexVal     = $hexValueList[bcmod($decStr, '16')].$hexVal;
            $decStr     = bcdiv($decStr, '16');
        }
        return $hexVal;
    }

    /**
     * @param string $hexStr
     * @return string
     * hexToDec
     */
    public static function hexToDec(string $hexStr):string
    {
        if(self::isEmpty($hexStr) || !preg_match('/^[a-fA-F\d]+$/', $hexStr)) return '';
        $hexStr         = strtolower($hexStr);
        $decValueList   = ['0' => '0', '1' => '1', '2' => '2', '3' => '3', '4' => '4', '5' => '5', '6' => '6', '7' => '7', '8' => '8', '9' => '9', 'a' => '10', 'b' => '11', 'c' => '12', 'd' => '13', 'e' => '14', 'f' => '15'];
        $decVal         = '0';
        $hexStr         = strrev($hexStr);
        $hexStrLength   = strlen($hexStr);
        for($i = 0; $i < $hexStrLength; $i++){
            $decVal     = bcadd(bcmul(bcpow('16', strval($i), 0), $decValueList[$hexStr[$i]]), $decVal);
        }
        return $decVal;
    }

    /**
     * @param array $decNumList
     * @return string
     * decToHexArray
     */
    public static function decToHexArray(array $decNumList):string
    {
        if(count($decNumList) < 1) return '';
        $hexStr             = '';
        foreach ($decNumList as $decNum){
            if(is_int($decNum)){
                if($decNum < 0) return '';
                $decNum     = strval($decNum);
            }
            if(!is_string($decNum)) return '';
            $tmpStr         = self::decToHex($decNum);
            $tmpStrLength   = strlen($tmpStr);
            if($tmpStrLength < 1 || $tmpStrLength > 16) return '';
            $hexStr         .= str_pad($tmpStr, 16, '0', STR_PAD_LEFT);
        }
        return $hexStr;
    }

    /**
     * @param string $hexStr
     * @return array
     * hexToDecArray
     */
    public static function hexToDecArray(string $hexStr):array
    {
        $hexStrLength       = strlen($hexStr);
        if($hexStrLength < 16 || $hexStrLength%16 !== 0) return [];
        $decNumList         = [];
        foreach(str_split($hexStr, 16) as $tmpHexStr){
            $tmpStr         = self::hexToDec($tmpHexStr);
            if(strlen($tmpStr) < 1 || !is_numeric($tmpStr)) return [];
            $decNumList[]   = $tmpStr;
        }
        return $decNumList;
    }

    /**
     * @param string $string
     * @param int $wordNum
     * @param int $wordMax
     * @param int $wordMin
     * @return bool
     * getCharStat
     */
    public static function getCharStat(string $string, int &$wordNum, int &$wordMax, int &$wordMin):bool
    {
        $splitAry   = self::strSplit($string, 1);
        if(count($splitAry) < 1) return false;
        $wordList   = [];
        foreach ($splitAry as $word){
            if(isset($wordList[$word])){
                ++$wordList[$word];
            }else{
                $wordList[$word] = 1;
            }
        }
        $wordNum    = count($wordList);
        arsort($wordList);
        $wordMax    = array_shift($wordList);
        $wordMin    = array_pop($wordList);
        return true;
    }

    /**
     * @param string $string
     * @param int $leftShowNum
     * @param int $rightShowNum
     * @param string $starStr
     * @param int $fillNum
     * @return string
     * genStarStr
     */
    public static function genStarStr(string $string, int $leftShowNum, int $rightShowNum, string $starStr = '*', int $fillNum = 0):string
    {
        $splitAry       = self::strSplit($string, 1);
        $splitArySize   = count($splitAry);
        if($splitArySize < 1) return '';
        $leftAry        = [];
        $rightAry       = [];
        if($leftShowNum > 0){
            if($leftShowNum < $splitArySize){
                $leftAry        = array_slice($splitAry, 0, $leftShowNum);
                $splitAry       = array_slice($splitAry, $leftShowNum);
                $splitArySize   = count($splitAry);
            }else{
                $leftAry        = $splitAry;
                $splitAry       = [];
                $splitArySize   = 0;
            }
        }
        if($rightShowNum > 0 && $splitArySize > 0){
            if($rightShowNum < $splitArySize){
                $rightAry       = array_slice($splitAry, $rightShowNum * -1);
                $splitAry       = array_slice($splitAry, 0, ($splitArySize - $rightShowNum));
                $splitArySize   = count($splitAry);
            }else{
                $rightAry       = $splitAry;
                $splitArySize   = 0;
            }
        }
        return implode('', array_merge_recursive($leftAry, array_fill(0, max($fillNum > 0 ? $fillNum : $splitArySize, 1), $starStr), $rightAry));
    }

    /**
     * @param string $strBool
     * @return bool
     * getBoolByString
     */
    public static function getBoolByString(string $strBool):bool
    {
        $strBoolUpper   = strtoupper($strBool);
        if($strBool === '1' || $strBoolUpper === 'ON' || $strBoolUpper === 'YES' || $strBoolUpper === 'TRUE') return true;
        if($strBool === '0' || $strBoolUpper === 'OFF' || $strBoolUpper === 'NO' || $strBoolUpper === 'FALSE') return false;
        return boolval($strBool);
    }

    /**
     * @param string $value
     * @return string
     * getStringByE
     */
    public static function getStringByE(string $value):string
    {
        if(!preg_match('/^(-?)(\d[\d.]*)E([+\-])(\d+)$/i', $value, $match)) return $value;
        $prefix         = $match[1] === '-' ? '-' : '';
        $numValue       = $match[2];
        $addSub         = $match[3];
        $numPowE        = $match[4];
        if(intval($numPowE) < 1) return $prefix.$numValue;
        if($addSub !== '+' && $addSub !== '-') return $value;
        $dotPos         = strpos($numValue, '.');
        $decimal        = $dotPos === false ? 0 : strlen(substr($numValue, $dotPos+1));
        if($addSub === '+'){
            $result     = bcmul($numValue, bcpow('10', $numPowE, $decimal), $decimal);
        }else{
            $decimal    += intval($numPowE);
            $result     = bcdiv($numValue, bcpow('10', $numPowE, $decimal), $decimal);
        }
        return $prefix.((strpos($result, '.') === false) ? $result : rtrim(rtrim($result, '0'), '.'));
    }

    /**
     * @param string $amountStrFloat
     * @param int $decimalSizeMax
     * @param int $decimalSizeMin
     * @param bool $isTruncate
     * @return string
     * getStrFormat
     */
    public static function getStrFormat(string $amountStrFloat, int $decimalSizeMax = 8, int $decimalSizeMin = 2, bool $isTruncate = false):string
    {
        $decimalSizeMin     = max($decimalSizeMin, 0);
        $decimalSize        = max($decimalSizeMax, $decimalSizeMin);
        if(preg_match('/^(\d+)\.(\d+)$/', $amountStrFloat, $match)){
            $amountInt      = intval($match[1]);
            $amountFloat    = intval($match[2]);
            $amountFloatStr = $match[2];
        }else if(preg_match('/^(\d+)\.$/', $amountStrFloat, $match)){
            $amountInt      = intval($match[1]);
            $amountFloat    = 0;
            $amountFloatStr = '';
        }else if(preg_match('/^(\d+)$/', $amountStrFloat, $match)){
            $amountInt      = intval($match[1]);
            $amountFloat    = 0;
            $amountFloatStr = '';
        }else{
            return '';
        }
        if($isTruncate){
            $formatString   = $amountInt.'.'.(strlen($amountFloatStr) < 1 ? '0' : substr($amountFloatStr, 0, $decimalSize));
        }else{
            $formatString   = sprintf('%.'.$decimalSize.'f', floatval($amountInt.'.'.($amountFloat === 0 ? '0' : $amountFloatStr)));
        }
        return self::formatFloatStr($formatString, $decimalSizeMin);
    }

    /**
     * @param float $amountFloat
     * @param int $decimalSizeMax
     * @param int $decimalSizeMin
     * @param bool $isTruncate
     * @return string
     * getFloatFormat
     */
    public static function getFloatFormat(float $amountFloat, int $decimalSizeMax = 8, int $decimalSizeMin = 2, bool $isTruncate = false):string
    {
        $decimalSizeMin     = max($decimalSizeMin, 0);
        $decimalSize        = max($decimalSizeMax, $decimalSizeMin);
        if($isTruncate){
            $amountPow      = pow(10, $decimalSize);
            $formatString   = sprintf('%.'.$decimalSize.'f', intval(strval($amountFloat*$amountPow))/$amountPow);
        }else{
            $formatString   = sprintf('%.'.$decimalSize.'f', $amountFloat);
        }
        return self::formatFloatStr($formatString, $decimalSizeMin);
    }

    /**
     * @param int $amount
     * @param int $decimalBase
     * @param int $decimalSizeMax
     * @param int $decimalSizeMin
     * @param bool $isTruncate
     * @return string
     * getAmountFormat
     */
    public static function getAmountFormat(int $amount, int $decimalBase, int $decimalSizeMax = 8, int $decimalSizeMin = 2, bool $isTruncate = false):string
    {
        if($decimalBase < 1) return strval($amount);
        $rateBaseNum        = pow(10, $decimalBase);
        $decimalSizeMin     = max($decimalSizeMin, 0);
        $decimalSize        = max($decimalSizeMax, $decimalSizeMin);
        if($isTruncate){
            $formatString   = bcdiv(strval($amount), strval($rateBaseNum), $decimalSize);
        }else{
            $formatString   = sprintf('%.'.$decimalSize.'f', $amount/$rateBaseNum);
        }
        return self::formatFloatStr($formatString, $decimalSizeMin);
    }

    /**
     * @param string $formatString
     * @param int $decimalSizeMin
     * @return string
     * formatFloatStr
     */
    private static function formatFloatStr(string $formatString, int $decimalSizeMin = 2):string
    {
        if(strlen($formatString) < 1) $formatString = '0';
        $pos                = strpos($formatString, '.');
        if($pos === false){
            $dotAddNum      = $decimalSizeMin;
            $formatString   .= $dotAddNum > 0 ? '.' : '';
        }else{
            $decimalStr     = rtrim(ltrim(substr($formatString, $pos), '.'), '0');
            $dotAddNum      = max($decimalSizeMin - strlen($decimalStr), 0);
            $formatString   = substr($formatString, 0, $pos + (($dotAddNum > 0 || strlen($decimalStr) > 0) ? 1 : 0)).$decimalStr;
        }
        while(--$dotAddNum >= 0) $formatString .= '0';
        return $formatString;
    }

    /**
     * @param string $className
     * @return string
     * getClassNameNamespace
     */
    public static function getClassNameNamespace(string $className):string
    {
        if(strlen($className) < 1 || $className === '\\') return '';
        $pos        = strrpos($className, '\\');
        if($pos < 1) return '';
        return substr($className, 0, $pos+1);
    }

    /**
     * @param string $className
     * @return string
     * getClassOnlyName
     */
    public static function getClassOnlyName(string $className):string
    {
        if(strlen($className) < 1 || $className === '\\') return '';
        $pos        = strrpos($className, '\\');
        if($pos < 1) return '';
        return substr($className, $pos+1);
    }

    /**
     * @param array $dataList
     * @param array $headList
     * @param string $title
     * @param array $confSet
     * @return string
     * getTableShellStr
     */
    public static function getTableShellStr(array $dataList, array $headList = [], string $title = '', array $confSet = []):string
    {
        $outString              = '';
        if(count($dataList) < 1) return $outString;
        $spaceHeadChar          = strval($confSet['spaceHead']??'-');       // 头尾填充符号
        $splitHeadChar          = strval($confSet['splitHead']??'+');       // 头尾分割符号
        $spaceDataChar          = strval($confSet['spaceData']??' ');       // 数据分割符号
        $splitDataChar          = strval($confSet['splitData']??'|');       // 数据分割符号
        $enterChar              = strval($confSet['enterChar']??"\n");      // 换行
        $isAutoEnter            = boolval($confSet['isAutoEnter']??1);      // 是否自动换行
        $maxColumn              = 64;                                       // 最大列宽度
        if(isset($confSet['maxColumn']) && is_int($confSet['maxColumn']) && $confSet['maxColumn'] >= 0) $maxColumn = $confSet['maxColumn'];
        if($maxColumn < 1) $maxColumn = 1024*1024*1024;
        $isMultiArray           = false;
        $isMultiIndex           = isset($dataList['___']);
        foreach($dataList as $lineIndex => $lineData){
            if(is_array($lineData)) $isMultiArray = true;
            if(!$isMultiArray) break;
            if($isMultiIndex) break;
            $dataList[$lineIndex]   = array_merge(['___' => is_string($lineIndex) ? $lineIndex : strval($lineIndex)], $lineData);
        }
        // 单数组
        if(!$isMultiArray){
            $keySizeMax             = 2;
            $valSizeMax             = 2;
            foreach($dataList as $key => $val){
                $keySizeMax         = min(max(strlen(self::getVarString($key))+2, $keySizeMax), $maxColumn);
                $valSizeMax         = min(max(strlen(self::getVarString($val)), $valSizeMax), $maxColumn);
            }
            $outTitle           = '';
            $outHeader          = $outFooter = $splitHeadChar.self::getStrPadString('', $keySizeMax, $spaceHeadChar, STR_PAD_BOTH).$splitHeadChar.self::getStrPadString('', $valSizeMax, $spaceHeadChar, STR_PAD_BOTH).$splitHeadChar.$enterChar;
            if(strlen($title) > 0){
                $titleSizeSet   = strlen($outFooter) - strlen($splitHeadChar.$splitHeadChar.$enterChar);
                $outTitle       .= $splitHeadChar.self::getStrPadString('', $titleSizeSet, $spaceHeadChar, STR_PAD_BOTH).$splitHeadChar.$enterChar;
                $outTitle       .= $splitHeadChar.self::getStrPadString($title, $titleSizeSet, $spaceDataChar, STR_PAD_BOTH, $keySizeMax+$valSizeMax).$splitHeadChar.$enterChar;
            }
            foreach($dataList as $key => $val){
                $keyString      = self::getVarString($key);
                $valString      = self::getVarString($val);
                while(true){
                    $leftKey        = '';
                    $outString  .= $splitDataChar.self::getStrPadString($keyString, $keySizeMax, $spaceDataChar, STR_PAD_LEFT, $maxColumn, $leftKey);
                    $keyString      = $leftKey;
                    $leftVal        = '';
                    $outString  .= $splitDataChar.self::getStrPadString($valString, $valSizeMax, $spaceDataChar, STR_PAD_RIGHT, $maxColumn, $leftVal);
                    $valString      = $leftVal;
                    $outString  .= $splitDataChar.$enterChar;
                    if(strlen($leftKey) < 1 && strlen($leftVal) < 1) break;
                    if(!$isAutoEnter) break;
                }
            }
            return $outTitle.$outHeader.$outString.$outFooter;
        }
        // 多维数组
        if(count($headList) < 1 || !isset($headList[0]) || !isset($headList[0]['name'])){
            $headList           = [];
            foreach($dataList as $lineData){
                if(!is_array($lineData) && count($lineData) < 1) continue;
                foreach($lineData as $key => $val){
                    $keyVal             = self::getVarString($key);
                    $valSize            = max(strlen(self::getVarString($val)), strlen($keyVal)+2);
                    $valSize            = min(max($valSize, 2, $headList[$keyVal]['column']??0), $maxColumn);
                    $headList[$keyVal]  = ['name' => $keyVal, 'column' => $valSize, 'type' => 2];
                }
            }
        }
        if(count($headList) > 0){
            $outTitle           = '';
            $outHeader          = '';
            $outFooter          = '';
            foreach($headList as $head){
                $outHeader      .= $splitHeadChar.self::getStrPadString($head['name'], $head['column'], $spaceHeadChar, STR_PAD_BOTH, $maxColumn);
                $outFooter      .= $splitHeadChar.self::getStrPadString('', $head['column'], $spaceHeadChar, STR_PAD_BOTH);
            }
            if(strlen($title) > 0){
                $titleSizeSet   = strlen($outFooter) - strlen($splitHeadChar);
                $outTitle       .= $splitHeadChar.self::getStrPadString('', $titleSizeSet, $spaceHeadChar, STR_PAD_BOTH).$splitHeadChar.$enterChar;
                $outTitle       .= $splitHeadChar.self::getStrPadString($title, $titleSizeSet, $spaceDataChar, STR_PAD_BOTH, $maxColumn).$splitHeadChar.$enterChar;
            }
            $outHeader          .= $splitHeadChar.$enterChar;
            $outFooter          .= $splitHeadChar.$enterChar;
            foreach($dataList as $item){
                $leftList       = [];
                $isLeft         = false;
                foreach($headList as $head){
                    $valueShow             = self::getVarString($item[$head['value']??'']??'');
                    if($valueShow === '') $valueShow = self::getVarString($item[$head['name']??'']??'');
                    $leftVal    = '';
                    $outString  .= $splitDataChar.self::getStrPadString($valueShow, $head['column'], $spaceDataChar, $head['type'], $maxColumn, $leftVal);
                    $leftList[] = [$leftVal, $head['column'], $spaceDataChar, $head['type'], $maxColumn];
                    if(strlen($leftVal) > 0){
                        $isLeft = true;
                    }
                }
                $outString  .= $splitDataChar.$enterChar;
                if(!$isAutoEnter) continue;
                while($isLeft){
                    $isLeft             = false;
                    foreach($leftList as &$leftItem){
                        $leftVal        = '';
                        $outString      .= $splitDataChar.self::getStrPadString($leftItem[0], $leftItem[1], $leftItem[2], $leftItem[3], $leftItem[4], $leftVal);
                        $leftItem[0]    = $leftVal;
                        if(strlen($leftVal) > 0){
                            $isLeft     = true;
                        }
                    }
                    $outString  .= $splitDataChar.$enterChar;
                }
            }
            $outString      = $outTitle.$outHeader.$outString.$outFooter;
        }
        return $outString;
    }

    /**
     * @param string $string
     * @param int $length
     * @param string $padString
     * @param int $type
     * @param int $maxSize
     * @param string $leftString
     * @return string
     * getStrPadString
     */
    private static function getStrPadString(string $string, int $length, string $padString, int $type, int $maxSize = 0, string &$leftString = ''):string
    {
        $allowTypeList  = [STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH];
        $string         = preg_replace('/\n/', ' ', $string);
        $leftString     = '';
        $diffSize       = 0;
        if($maxSize > 0){
            if(preg_match_all("/[\x01-\x7f]|[\xc2-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xff][\x80-\xbf]{3}/", $string, $match)){
                $tmpString      = '';
                $tmpSize        = 0;
                foreach($match[0] as $tmpVal){
                    if(preg_match("/^[\x01-\x7f]$/", $tmpVal)){
                        ++$tmpSize;
                        if($tmpSize > $length) break;
                    }else{
                        $tmpSize        += 2;
                        ++$diffSize;
                        $diffSize       = min($diffSize, ($length - $tmpSize));
                        if($tmpSize > $length){
                            $diffSize   = $length - $tmpSize + 2;
                            break;
                        }
                    }
                    $tmpString      .= $tmpVal;
                    if($tmpSize == $length){
                        $diffSize   = 0;
                        break;
                    }
                }
            }else{
                $tmpString  = grapheme_substr($string, 0, $maxSize);
                if(!is_string($tmpString)) $tmpString = mb_substr($string, 0, $maxSize);
            }
            $leftString     = substr($string, strlen($tmpString));
            $resString      = str_pad($tmpString, $length, $padString, in_array($type, $allowTypeList, true) ? $type : STR_PAD_BOTH);
        }else{
            $resString      = str_pad($string, $length, $padString, in_array($type, $allowTypeList, true) ? $type : STR_PAD_BOTH);
        }
        if($diffSize > 0) $resString .= str_pad('', $diffSize, $padString);
        return $resString;
    }

    /**
     * @param $var
     * @return string
     * getVarString
     */
    public static function getVarString($var):string
    {
        if(is_int($var) || is_string($var) || is_numeric($var) || is_float($var)){
            $valString      = strval($var);
        }else if(is_bool($var)){
            $valString      = $var ? 'true' : 'false';
        }else if(is_null($var)){
            $valString      = 'null';
        }else if(is_array($var)){
            $valString      = CommJson::encodeArray($var);
        }else{
            $valString      = serialize($var);
        }
        return $valString;
    }

    /**
     * @param int $columnNum
     * @return string
     * getExcelColumn
     */
    public static function getExcelColumn(int $columnNum):string
    {
        $columnStr      = '';
        if($columnNum < 1) return $columnStr;
        do {
            $charNum    = ($columnNum - 1) % 26;
            $columnStr  = chr(65 + $charNum).$columnStr;
            $columnNum  = floor(($columnNum - $charNum) / 26);
        } while ($columnNum > 0);
        return $columnStr;
    }

    /**
     * @param string $version
     * @param int $verMaxNum
     * @param bool $isEnd
     * @return string
     * getVersionStr
     */
    public static function getVersionStr(string $version, int $verMaxNum = 3, bool $isEnd = false):string
    {
        if($verMaxNum < 1 || strlen($version) < 1) return '';
        $versionList        = explode('.', preg_replace('/\.+/', '.', $version));
        $versionListSize    = count($versionList);
        if($versionListSize <= $verMaxNum) return implode('.', $versionList);
        return implode('.', $isEnd ? array_slice($versionList, $verMaxNum*-1) : array_slice($versionList, 0, $verMaxNum));
    }

    /**
     * @param string $string
     * @param array $tplData
     * @param bool $replaceNoSet
     * @return string
     * replaceTplStr
     */
    public static function replaceTplStr(string $string, array $tplData, bool $replaceNoSet = false):string
    {
        if(!preg_match_all("/{([\w\-._]+)}/", $string, $match)) return $string;
        $search                 = [];
        $replace                = [];
        foreach($match[1] as $val){
            $searchKey          = '\'{'.$val.'}\'';
            if(in_array($searchKey, $search, true)) continue;
            if(!$replaceNoSet && !isset($tplData[$val])) continue;
            $search[]           = $searchKey;
            $replace[]          = strval($tplData[$val]??'');
        }
        if(count($search) > 0) $string = preg_replace($search, $replace, $string);
        return $string;
    }

    /**
     * @return void
     * genCrc64Table
     */
    private static function genCrc64Table():void
    {
        if(!self::$crc64Init){
            self::$crc64Init        = true;
            self::$crc64TableList   = [];
            $poly64rev = (0xC96C5795 << 32) | 0xD7870F42;
            for($i = 0; $i < 256; $i++){
                for($part = $i, $bit = 0; $bit < 8; $bit++){
                    if($part & 1){
                        $part       = (($part >> 1) & ~(0x8 << 60)) ^ $poly64rev;
                    }else{
                        $part       = ($part >> 1) & ~(0x8 << 60);
                    }
                }
                self::$crc64TableList[$i] = $part;
            }
        }
    }

    /**
     * @param string $srcStr
     * @param string $format
     * @return string
     * crc64
     */
    public static function crc64(string $srcStr, string $format):string
    {
        $srcStrLength   = strlen($srcStr);
        if($srcStrLength < 1) return '';
        self::genCrc64Table();
        $crc64          = 0;
        for($i = 0; $i < $srcStrLength; $i++) $crc64 = self::$crc64TableList[($crc64 ^ ord($srcStr[$i])) & 0xff] ^ (($crc64 >> 8) & ~(0xff << 56));
        return sprintf($format, $crc64);
    }

    /**
     * @param string $srcStr
     * @return string
     * crc64HexLower
     */
    public static function crc64HexLower(string $srcStr):string
    {
        return self::crc64($srcStr, '%x');
    }

    /**
     * @param string $srcStr
     * @return string
     * crc64HexUpper
     */
    public static function crc64HexUpper(string $srcStr):string
    {
        return self::crc64($srcStr, '%X');
    }

    /**
     * @param string $srcStr
     * @return string
     * crc64DecStr
     */
    public static function crc64DecStr(string $srcStr):string
    {
        return self::crc64($srcStr, '%u');
    }

    /**
     * @param string $srcStr
     * @param bool $isFail
     * @return int
     * crc64DecInt
     */
    public static function crc64DecInt(string $srcStr, bool &$isFail = false):int
    {
        $crc64Str   = self::crc64($srcStr, '%d');
        $isFail     = strlen($crc64Str) < 1;
        return intval($crc64Str);
    }

    /**
     * @param int $int64
     * @return string
     * int64ToUnsignedStr
     */
    public static function int64ToUnsignedStr(int $int64):string
    {
        return sprintf('%u', $int64);
    }

    /**
     * @param string $int64Str
     * @param bool $int64Fail
     * @return int
     * UnsignedStrToInt64
     */
    public static function UnsignedStrToInt64(string $int64Str, bool &$int64Fail = false):int
    {
        if(bccomp($int64Str, strval(PHP_INT_MAX)) !== 1) return intval($int64Str);
        $uInt64MaxStr   = bcpow('2', '64');
        if(bccomp($int64Str, $uInt64MaxStr) !== 1) return intval(bcsub($int64Str, $uInt64MaxStr));
        $int64Fail      = true;
        return 0;
    }

    /**
     * @param int $v
     * @param int $n
     * @return int
     * BitRRR无符号按位右移
     */
    public static function BitRRR(int $v, int $n):int
    {
        return ($v & 0xFFFFFFFF) >> ($n & 0x1F);
    }

    /**
     * @param int $v
     * @param int $n
     * @return int
     * BitRR按位右移
     */
    public static function BitRR(int $v, int $n):int
    {
        return ($v & 0x80000000 ? $v | 0x7FFFFFFF00000000 : $v & 0xFFFFFFFF) >> ($n & 0x1F);
    }

    /**
     * @param int $v
     * @param int $n
     * @return int
     * BitLL按位左移
     */
    public static function BitLL(int $v, int $n):int
    {
        return ($t = ($v & 0xFFFFFFFF) << ($n & 0x1F)) & 0x80000000 ? $t | 0x7FFFFFFF00000000 : $t & 0xFFFFFFFF;
    }
}